import { h } from "koishi"

export const name = "command-arg-default"

export const inject = ["database"]

export function apply(ctx) {
  ctx.model.extend(
    "command_arg_default",
    {
      platform: "string",
      channelId: "string",
      command: "string",
      argIndex: "unsigned",
      value: "text",
    },
    {
      primary: ["platform", "channelId", "command", "argIndex"],
    },
  )

  ctx.before("command/execute", async ({ session, command, args }) => {
    const { platform, channelId } = session
    for (const { argIndex, value } of await ctx.database.get("command_arg_default", { platform, channelId, command: command.name })) {
      const { argDecl } = getArg(command, argIndex)
      if (!argDecl) {
        ctx.logger.warn("arg not found", { platform, channelId, command: command.name, argIndex })
        continue
      }
      const { name, type } = argDecl
      ctx.logger.debug({ platform, channelId, command: command.name, argIndex, name, type, value })
      args[argIndex] ??= ctx.$commander.resolveDomain(type || "string").transform(value)
    }
  })

  function parseChannel(spec) {
    const platform = spec.split(":")[0]
    const channelId = spec.slice(platform.length + 1)
    return { platform, channelId }
  }

  function getArg(command, argIndex, session) {
    let argDecl = command._arguments[argIndex]
    if (!argDecl && command._arguments.at(-1)?.variadic) argDecl = command._arguments.at(-1)
    return {
      argDecl,
      argName: argDecl?.name || "",
      argDesc: argDecl ? session?.i18n(`commands.${command.name}.arguments.${argDecl.name}`) : null,
    }
  }

  ctx.command("defaultarg <...command-name>", { strictOptions: true, authority: 3 })
    .option("arg", ". <index:natural>")
    .option("rest", "-- <value:elements>")
    .option("remove", "-r")
    .option("channel", "-c <channel:channel>", { descPath: "admin.channel-option" })
    .action(async (
      {
        session,
        options: { arg: argIndex, rest, remove, channel },
      },
      ...nameParts
    ) => {
      if (!nameParts.length && (rest != null || argIndex != null))
        return session.i18n(".expect-command-name")

      const { platform, channelId } = channel ? parseChannel(channel) : session

      let command
      if (nameParts.length) {
        command = ctx.$commander.resolve(nameParts.join("."), session)
        if (!command)
          return session.i18n(".command-not-found", { command: nameParts.join(" ") })
      }

      if (rest != null) {
        argIndex ??= 0
        const value = Array.isArray(rest) ? rest.join("") : rest
        const fauxArgv = { session }
        const { argDecl, argName, argDesc } = getArg(command, argIndex, session)
        ctx.$commander.parseValue(value, "argument", fauxArgv, argDecl)
        if (fauxArgv.error) return fauxArgv.error

        await ctx.database.upsert("command_arg_default", [{
          platform,
          channelId,
          command: command.name,
          argIndex,
          value,
        }])
        return session.i18n(".saved", {
          command: command.displayName,
          argIndex,
          argName,
          argDesc,
          value: rest,
        })
      }

      if (remove) {
        if (argIndex == null) {
          const { removed } = await ctx.database.remove("command_arg_default", {
            platform,
            channelId,
            command: command.name,
          })
          return session.i18n(removed ? ".removed-all-command" : ".empty-command", {
            command: command.displayName,
            removed,
          })
        }

        const { argName, argDesc } = getArg(command, argIndex, session)
        const { removed } = await ctx.database.remove("command_arg_default", {
          platform,
          channelId,
          command: command.name,
          argIndex,
        })
        return session.i18n(removed ? ".removed" : ".not-found", {
          command: command.displayName,
          argIndex,
          argName,
          argDesc,
        })
      }

      const query = { platform, channelId }
      if (command) query.command = command.name
      if (argIndex != null) query.argIndex = argIndex
      const data = await ctx.database.get("command_arg_default", query)
      if (!data.length) {
        if (argIndex != null)
          return session.i18n(".not-found", {
            command: command.displayName,
            ...getArg(command, argIndex, session),
          })
        if (command)
          return session.i18n(".empty-command", {
            command: command.displayName,
          })
        return session.i18n(".empty")
      }
      return data.map(({ command: name, argIndex, value }) => {
        const command = ctx.$commander.get(name, session)
        return h("p", session.i18n(".entry", {
          command: command.displayName,
          argIndex,
          ...getArg(command, argIndex, session),
          value: h.parse(value),
        }))
      })
    })

  ctx.i18n.define("zh-CN", "commands.defaultarg", {
    description: "频道指令参数默认值",
    arguments: {
      "command-name": "指令名称",
    },
    options: {
      arg: "参数索引",
      rest: "设置默认值",
      remove: "移除默认值",
    },
    messages: {
      "expect-command-name": "缺少指令名称…",
      "command-not-found": "未找到指令 {command}…",
      saved: "已为该频道指令 {command} 参数 {argDesc || argIndex} 设置默认值：{value}",
      "removed-all-command": "已为该频道移除指令 {command} 的 {removed} 个参数默认值。",
      removed: "已为该频道移除指令 {command} 参数 {argDesc || argIndex} 的默认值。",
      empty: "该频道未设置任何指令参数默认值…",
      "empty-command": "该频道未设置指令 {command} 的参数默认值…",
      "not-found": "该频道未设置指令 {command} 参数 {argDesc || argIndex} 的默认值…",
      entry: "指令 {command} 参数 {argDesc || argIndex} 的默认值为：{value}",
    },
  })
}
